﻿#region Copyright
/// <copyright>
/// Copyright (c) 2008 Ilia Shramko.
/// Released under the terms of the GNU General Public License.
/// </copyright>
#endregion

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace Osh
{
	public abstract class PacketStream : Stream, IPacketStream
	{
		private bool _endOfStream;
		private bool _readThrough;
		private byte[] _packetBuffer;
		private int _packetSize;
		private int _packetPosition;

		public PacketStream() : this(false)
		{
		}

		public PacketStream(bool readThrough)
		{
			_readThrough = readThrough;
		}

		protected bool Eof
		{
			get { return _endOfStream; }
			set { _endOfStream = value; }
		}

		/// <summary>
		/// Gets or sets mode in which the stream will read data.
		/// If true the stream will automaticaly move to the next packet when the current packet was fully read.
		/// If false the stream has to be moved to the next packet by calling MoveToNextPacket,
		/// otherwise an exception will be rised when there is no enough data in the current packet.
		/// Default is false.
		/// </summary>
		public bool ReadThrough
		{
			get { return _readThrough; }
			set { _readThrough = value; }
		}

		protected void FillPacketBuffer(byte[] buffer, int offset, int length)
		{
			if(_packetBuffer == null || _packetBuffer.Length < length)
				_packetBuffer = new byte[length];

			Buffer.BlockCopy(buffer, offset, _packetBuffer, 0, length);
			_packetSize = length;
			_packetPosition = 0;
		}

		protected void EnsurePacketBufferSize(int size)
		{
			if(_packetBuffer == null || _packetBuffer.Length < size)
				_packetBuffer = new byte[size];
		}

		#region Stream overrides
		public override bool CanRead
		{
			get { return true; }
		}

		public override bool CanWrite
		{
			get { return false; }
		}

		public override bool CanSeek
		{
			get { return false; }
		}

		public override long Length
		{
			get { throw new NotSupportedException(); }
		}

		public override long Position
		{
			get { throw new NotSupportedException(); }
			set { throw new NotSupportedException(); }
		}

		public override void Flush()
		{
			throw new NotSupportedException();
		}

		public override long Seek(long offset, SeekOrigin origin)
		{
			if(origin != SeekOrigin.Current)
				throw new NotSupportedException();

			if(this.ReadThrough)
			{
				throw new NotSupportedException();
			}
			else
			{
				this.PacketSeek((int)offset, origin);
				return -1;
			}
		}

		public override void SetLength(long value)
		{
			throw new NotSupportedException();
		}

		public override int Read(byte[] buffer, int offset, int count)
		{
			if(buffer == null)
				throw new ArgumentOutOfRangeException("buffer");
			if(offset < 0)
				throw new ArgumentOutOfRangeException("offset");
			if(count < 0)
				throw new ArgumentOutOfRangeException("count");
			if(buffer.Length - offset < count)
				throw new ArgumentOutOfRangeException("count");

			if(_endOfStream)
				return 0;

			if(_packetPosition + count > _packetSize && !_readThrough)
				throw new ArgumentOutOfRangeException("count", "Not enough data in the current packet.");

			int read = 0;
			while(count > 0)
			{
				if(_packetPosition >= _packetSize)
				{
					_endOfStream = !MoveToNextPacket();
					if(_endOfStream) break;
				}

				int toRead = Math.Min(_packetSize - _packetPosition, count);
				Buffer.BlockCopy(_packetBuffer, _packetPosition, buffer, offset, toRead);
				_packetPosition += toRead;
				read += toRead;
				offset += toRead;
				count -= toRead;
			}

			return read;
		}

		public override void Write(byte[] buffer, int offset, int count)
		{
			throw new NotSupportedException();
		}

		public override void Close()
		{
			_endOfStream = true;
			_packetBuffer = null;
		}
		#endregion Stream overrides

		#region IPacketStream
		public bool MoveToNextPacket()
		{
			if(_endOfStream)
				return false;

			if(MoveToNextPacketInternal())
			{
				_packetPosition = 0;
				return true;
			}

			_packetPosition = 0;
			_packetSize = 0;
			this.Eof = true;
			return false;
		}

		protected abstract bool MoveToNextPacketInternal();

		public virtual byte[] PacketBuffer
		{
			get { return _packetBuffer; }
			protected set { _packetBuffer = value; }
		}

		public virtual int PacketSize
		{
			get { return _packetSize; }
			protected set { _packetSize = value; }
		}

		public virtual int PacketPosition
		{
			get { return _packetPosition; }
			protected set { _packetPosition = value; }
		}

		public virtual int PacketSeek(int offset, SeekOrigin origin)
		{
			if(_packetBuffer == null || _endOfStream || _packetSize <= 0)
				throw new ObjectDisposedException(null);

			int newPosition;
			if(origin == SeekOrigin.Current)
				newPosition = _packetPosition + offset;
			else if(origin == SeekOrigin.Begin)
				newPosition = offset;
			else if(origin == SeekOrigin.End)
				newPosition = _packetSize + offset;
			else
				throw new ArgumentOutOfRangeException("origin");

			if(newPosition < 0 || newPosition > _packetSize)
			{
				//Debug.Assert(false);
				throw new ArgumentOutOfRangeException("offset");
			}

			_packetPosition = newPosition;
			return _packetPosition;
		}
		#endregion IPacketStream

	}
}
